package cRpc

import (
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/gin-gonic/gin"

	"gitee.com/csingo/cCommon"
	"gitee.com/csingo/cContext"
	"gitee.com/csingo/cHelper"
	"gitee.com/csingo/cResponse"
)

type RpcController struct{}

func (i *RpcController) ControllerName() (app, name string) {
	return "csingo", "RpcController"
}

func (i *RpcController) Handler(ctx *gin.Context) (err error) {
	app := ctx.GetHeader(cCommon.Header_Rpc_App)
	service := ctx.GetHeader(cCommon.Header_Rpc_Service)
	method := ctx.GetHeader(cCommon.Header_Rpc_Method)
	sign := ctx.GetHeader(cCommon.Header_Rpc_Sign)
	data := ctx.GetHeader(cCommon.Header_Rpc_Context)
	noSignature := cHelper.ToBool(ctx.GetHeader(cCommon.Header_Rpc_NoSignature))
	signature := client_container.GetSignature()
	if signature == "" {
		signature = rpc_config.Signature
	}

	if data != "" {
		t := reflect.TypeOf(metadata_config.Context)
		metadata := reflect.New(t)
		_ = json.Unmarshal([]byte(data), metadata.Interface())
		for metadata.Kind() == reflect.Ptr {
			metadata = metadata.Elem()
		}
		metadataType := metadata.Type()
		fields := metadataType.NumField()
		for index := 0; index < fields; index++ {
			tag := metadataType.Field(index).Tag.Get("json")
			if tag == "" {
				tag = metadataType.Field(index).Name
			}

			ctx.Set(tag, metadata.Field(index).Interface())
		}
	}

	// 签名校验
	var pass bool
	if sign == "" {
		return cResponse.Api(ctx, nil, fmt.Errorf("sign is empty"))
	}
	if noSignature {
		source := cContext.GetTraceId(ctx)
		checkSign := cHelper.Md5([]byte(source))
		pass = sign == checkSign
	} else if rpc_config.SignDecryptFunc != nil {
		pass = rpc_config.SignDecryptFunc(ctx, app, service)
	} else if signature != "" {
		source := fmt.Sprintf("%s:%s", signature, cContext.GetTraceId(ctx))
		checkSign := cHelper.Md5([]byte(source))
		pass = sign == checkSign
	} else {
		source := cContext.GetTraceId(ctx)
		checkSign := cHelper.Md5([]byte(source))
		pass = sign == checkSign
	}
	if !pass {
		return cResponse.Api(ctx, nil, fmt.Errorf("sign is invalid"))
	}

	index := app + "." + service
	instance := service_container.Get(index)
	if instance == nil {
		return cResponse.Api(ctx, nil, fmt.Errorf("rpc service not found: %s.%s", app, service))
	}

	caller := reflect.ValueOf(instance).MethodByName(method)
	if !caller.IsValid() || caller.IsNil() {
		return cResponse.Api(ctx, nil, fmt.Errorf("rpc service method not found: %s.%s.%s", app, service, method))
	}

	typ := caller.Type().In(1)
	req := reflect.New(typ)

	err = ctx.ShouldBind(req.Interface())
	if err != nil {
		return cResponse.Api(ctx, nil, fmt.Errorf("rpc service method request data bind fail: %s.%s.%s %s", app, service, method, err))
	}

	params := []reflect.Value{
		reflect.ValueOf(ctx),
		req.Elem(),
	}
	result := caller.Call(params)
	if len(result) != 2 {
		return cResponse.Api(ctx, nil, fmt.Errorf("rpc service method return invaild result: %s.%s.%s", app, service, method))
	}
	if !result[1].IsNil() {
		return cResponse.Api(ctx, nil, result[1].Interface().(error))
	}

	return cResponse.Api(ctx, result[0].Interface(), nil)
}
